package cn.edu.hitsz.compiler.parser;

import cn.edu.hitsz.compiler.NotImplementedException;
import cn.edu.hitsz.compiler.ir.IRImmediate;
import cn.edu.hitsz.compiler.ir.IRValue;
import cn.edu.hitsz.compiler.ir.IRVariable;
import cn.edu.hitsz.compiler.ir.Instruction;
import cn.edu.hitsz.compiler.lexer.Token;
import cn.edu.hitsz.compiler.lexer.TokenKind;
import cn.edu.hitsz.compiler.parser.table.NonTerminal;
import cn.edu.hitsz.compiler.parser.table.Production;
import cn.edu.hitsz.compiler.parser.table.Status;
import cn.edu.hitsz.compiler.symtab.SourceCodeType;
import cn.edu.hitsz.compiler.symtab.SymbolTable;
import cn.edu.hitsz.compiler.utils.FileUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

// TODO: 实验三: 实现 IR 生成

/**
 *
 */
public class IRGenerator implements ActionObserver {

    List<Instruction> instructions = new ArrayList<>();
    private class Node
    {
        private IRValue val;

        public Node()
        {
            val = null;
        }
        public Node(IRValue val)
        {
            this.val = val;
        }
    }
    SymbolTable symbolTable;

    List<Node> stack = new ArrayList<>();
    @Override
    public void whenShift(Status currentStatus, Token currentToken) {
        if (currentToken.getKind().getCode() == 51)
        {
            stack.add(new Node(IRVariable.named(currentToken.getText())));
        }
        else if (currentToken.getKind().getCode() == 52)
        {
            stack.add(new Node(IRImmediate.of(Integer.parseInt(currentToken.getText()))));
        }
        else
        {
            stack.add(new Node());
        }
    }

    @Override
    public void whenReduce(Status currentStatus, Production production) {
        Node node = new Node();
        switch (production.index())
        {
            case 6 :// S -> id = E
            {
                Node id = stack.get(stack.size()-3);
                Node E = stack.get(stack.size()-1);
                instructions.add(Instruction.createMov((IRVariable) id.val, E.val));
                break;
            }
            case 7 :// S -> return E
            {
                Node E = stack.get(stack.size()-1);
                instructions.add(Instruction.createRet(E.val));
                break;
            }
            case 8 :// E -> E + A
            {
                node.val = IRVariable.temp();
                Node E = stack.get(stack.size()-3);
                Node A = stack.get(stack.size()-1);
                instructions.add(Instruction.createAdd((IRVariable) node.val, E.val, A.val));
                break;
            }
            case 9 :// E -> E - A
            {
                node.val = IRVariable.temp();
                Node E = stack.get(stack.size()-3);
                Node A = stack.get(stack.size()-1);
                instructions.add(Instruction.createSub((IRVariable) node.val, E.val, A.val));
                break;
            }
            case 10:// E -> A
            {
                Node A = stack.get(stack.size()-1);
                node.val = A.val;
                break;
            }
            case 11:// A -> A * B
            {
                node.val = IRVariable.temp();
                Node A = stack.get(stack.size()-3);
                Node B = stack.get(stack.size()-1);
                instructions.add(Instruction.createMul((IRVariable) node.val, A.val, B.val));
                break;
            }
            case 12:// A -> B
            {
                Node B = stack.get(stack.size()-1);
                node.val = B.val;
                break;
            }
            case 13:// B -> ( E )
            {
                Node E = stack.get(stack.size()-2);
                node.val = E.val;
                break;
            }
            case 14:// B -> id
            {
                Node id = stack.get(stack.size()-1);
                node.val = id.val;
                break;
            }
            case 15:// B -> IntConst
            {
                Node IntConst = stack.get(stack.size()-1);
                node.val = IntConst.val;
                break;
            }
            default:
                break;
        }
        List<String> bodyStr = Arrays.stream(production.toString().split(" ")).toList();
        for(int i=0;i<bodyStr.size()-2;i++)
        {
            stack.remove(stack.size()-1);
        }
        stack.add(node);
    }


    @Override
    public void whenAccept(Status currentStatus) {
        //do nothing
    }

    @Override
    public void setSymbolTable(SymbolTable table) {
        symbolTable = table;
    }

    public List<Instruction> getIR() {
        return instructions;
    }

    public void dumpIR(String path) {
        FileUtils.writeLines(path, getIR().stream().map(Instruction::toString).toList());
    }
}

